
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::{ CanvasRenderingContext2d, ImageData };

use lazy_static::lazy_static;

use std::collections::HashMap;
use std::sync::Mutex;

use crate::geometry::{ Point };

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log(s: &str);
}

macro_rules! console_log_rs {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

lazy_static! {
    static ref BOARD: Mutex<[[i32; 10]; 20]> = Mutex::new([[0;10]; 20]);
}

// 第row行往下掉
fn falling(row: usize) {
    let data = &mut *BOARD.lock().unwrap();
    console_log_rs!("Falling={}", row);
    for i in (0..row).rev() {
        for col in 0..data[0].len() {
            data[i+1][col] = data[i][col];
        }
    }
}

type Block = [Point; 4];
type BoxGroup = [Block; 4];

#[derive(Clone, Copy)]
pub struct Square {
    idx: usize,
    offset: (f64, f64),
    cur_block: Block,
    possible_block: BoxGroup
}

#[derive(Clone, Copy)]
pub struct Shape {
    possible_shape: [BoxGroup; 7]
}

impl Shape {
    pub fn new() -> Self {
        // 竖7
        let block1 = [
            [ Point::new(0.,2.), Point::new(1.,2.), Point::new(1.,1.), Point::new(1.,0.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(0.,2.), Point::new(1.,2.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(1.,2.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(0.,1.), Point::new(0.,2.) ],
        ];

        // 闪电⚡
        let block2 = [
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(2.,1.) ],
            [ Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.), Point::new(0.,2.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(2.,1.) ],
            [ Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.), Point::new(0.,2.) ]
        ];

        // 闪电⚡
        let block3 = [
            [ Point::new(1.,0.), Point::new(2.,0.), Point::new(0.,1.), Point::new(1.,1.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(1.,1.), Point::new(1.,2.) ],
            [ Point::new(1.,0.), Point::new(2.,0.), Point::new(0.,1.), Point::new(1.,1.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(1.,1.), Point::new(1.,2.) ]
        ];

        // 凸块
        let block4 = [
            [ Point::new(1.,0.), Point::new(0.,1.), Point::new(1.,1.), Point::new(2.,1.) ],
            [ Point::new(1.,1.), Point::new(0.,0.), Point::new(0.,1.), Point::new(0.,2.) ],
            [ Point::new(1.,1.), Point::new(0.,0.), Point::new(1.,0.), Point::new(2.,0.) ],
            [ Point::new(0.,1.), Point::new(1.,0.), Point::new(1.,1.), Point::new(1.,2.) ]
        ];

        // 横7
        let block5 = [
            [ Point::new(0.,1.), Point::new(1.,1.), Point::new(2.,1.), Point::new(2.,0.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(1.,1.), Point::new(2.,1.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(2.,0.), Point::new(2.,1.) ],
            [ Point::new(0.,1.), Point::new(0.,0.), Point::new(1.,0.), Point::new(2.,0.) ]
        ];

        // 方块
        let block6 = [
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(1.,1.), Point::new(0.,1.) ]
        ];

        // 条块
        let block7 = [
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(2.,0.), Point::new(3.,0.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(0.,2.), Point::new(0.,3.) ],
            [ Point::new(0.,0.), Point::new(1.,0.), Point::new(2.,0.), Point::new(3.,0.) ],
            [ Point::new(0.,0.), Point::new(0.,1.), Point::new(0.,2.), Point::new(0.,3.) ]
        ];

        Shape {
            possible_shape: [block1, block2, block3, block4, block5, block6, block7 ]
        }
    }

    fn generator(&self, rand_idx: usize) -> BoxGroup {
        self.possible_shape[rand_idx]
    }
}

impl Square {

    pub fn new(shape: Shape, idx: i32) -> Self {
        console_log_rs!("Square:::new:: {}", idx);
        let possible_block = shape.generator(idx as usize);
        let idx = (idx % 4) as usize;
        let cur_block = possible_block[idx];
        Square{ cur_block, possible_block, idx, offset: (0.0, 0.0) }
    }

    pub fn update_state_sucess(&mut self) -> bool {
        if !self.is_at_bottom() {
            self.clear_rect();
            self.decline();
            self.draw();
            return true;
        }
        return false;
    }

    pub fn eliminate_if_need(&mut self) {
        let data = *BOARD.lock().unwrap();
        let mut lines: HashMap<usize, bool> = HashMap::with_capacity(4);
        for row in 0..data.len() {
            let mut full = true;
            for col in 0..data[0].len() {
                if data[row][col] != 1 {
                    full = false;
                    break;
                }
            }
            if full {
                lines.insert(row, true);
            }
        }

        for row in 0..data.len() {
            if let Some(eliminate) = lines.get(&row) {
                falling(row);
            }
        }

        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document.get_element_by_id("canvas").unwrap();
        let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        let context = canvas.get_context("2d")
            .unwrap().unwrap()
            .dyn_into::<web_sys::CanvasRenderingContext2d>()
            .unwrap();

        let data = *BOARD.lock().unwrap();
        for row in 0..data.len() {
            for col in 0..data[0].len() {
                if data[row][col] == 1 {
                    context.set_fill_style(&JsValue::from_str("red"));
                    context.set_stroke_style(&JsValue::from_str("black"));
                    context.set_line_width(1.0);
                    context.fill_rect(col as f64 * 10.0, row as f64 * 10.0, 10.0, 10.0);
                    context.stroke_rect(col as f64 * 10.0, row as f64 * 10.0, 10.0, 10.0);
                } else {
                    context.clear_rect(col as f64 * 10.0, row as f64 * 10.0, 10.0, 10.0);
                }
            }
        }
    }

    pub fn key_up(&mut self) {
        if !self.is_at_bottom() {
            self.clear_rect();
            self.inc_idx();
            self.draw();
        } else {
            console_log_rs!("can't transform.......");
        }
    }

    pub fn key_down(&mut self) {
        if !self.is_at_bottom() {
            self.clear_rect();
            self.decline();
        }
    }

    pub fn key_space(&mut self) {
        while !self.is_at_bottom() {
            self.clear_rect();
            self.decline();
            self.draw();
        }
    }

    pub fn key_left(&mut self) {
        if self.can_shift(|x,off|x - off) {
            self.clear_rect();
            self.shift(1.0, |x,off|x - off);
            self.draw();
        }
    }

    pub fn key_right(&mut self) {
        if self.can_shift(|x,off|x + off) {
            self.clear_rect();
            self.shift(1.0, |x,off|x + off);
            self.draw();
        }
    }

    pub fn decline(&mut self) {
        self.offset.1 += 1.0;
        for cnt in 0..4 {
            self.cur_block[cnt].y += 1.0;
        }
    }

    pub fn shift(&mut self, off: f64, sft: fn(f64, f64) -> f64) {
        self.offset.0 = sft(self.offset.0, 1.0);
        for cnt in 0..4 {
            self.cur_block[cnt].x = sft(self.cur_block[cnt].x, off);
        }
    }

    pub fn can_shift(&self, sft: fn(f64, f64) -> f64) -> bool {
        let data = &mut *BOARD.lock().unwrap();
        for cnt in 0..4 {
            let x = sft(self.cur_block[cnt].x, 1.0) as i32;
            let y = self.cur_block[cnt].y as usize;
            if x < 0 || x >= 10 || data[y][x as usize] == 1 {
                return false;
            }
        }
        return true;
    }

    pub fn is_at_bottom(&self) -> bool {
        let mut at_bottom = false;
        let data = &mut BOARD.lock().unwrap();
        for cnt in 0..4 {
            let x = self.cur_block[cnt].x as usize;
            let y = (self.cur_block[cnt].y + 1.0) as usize;
            if y >= 20 || data[y][x] == 1 {
                at_bottom = true;
                break;
            }
        }
        if at_bottom {
            for cnt in 0..4 {
                let x = self.cur_block[cnt].x as usize;
                let y = self.cur_block[cnt].y as usize;
                data[y][x] = 1;
            }
        }
        at_bottom
    }

    pub fn game_over(&self) -> bool {
        for cnt in 0..4 {
            if self.cur_block[cnt].y <= 0.0 {
                console_log_rs!("GAME --------------  OVER");
                return true;
            }
        }
        return false;
    }


    pub fn draw(&self) {
        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document.get_element_by_id("canvas").unwrap();
        let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        let context = canvas.get_context("2d")
            .unwrap().unwrap()
            .dyn_into::<web_sys::CanvasRenderingContext2d>()
            .unwrap();
        for point in self.cur_block.iter() {
            context.set_fill_style(&JsValue::from_str("red"));
            context.set_stroke_style(&JsValue::from_str("black"));
            context.set_line_width(1.0);
            context.fill_rect(point.x * 10.0, point.y * 10.0, 10.0, 10.0);
            context.stroke_rect(point.x * 10.0, point.y * 10.0, 10.0, 10.0);
        }
    }

    pub fn clear_rect(&self) {
        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document.get_element_by_id("canvas").unwrap();
        let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>()
            .map_err(|_| ())
            .unwrap();

        let context = canvas.get_context("2d")
            .unwrap().unwrap()
            .dyn_into::<web_sys::CanvasRenderingContext2d>()
            .unwrap();

        for cnt in 0..4 {
            let x = self.cur_block[cnt].x;
            let y = self.cur_block[cnt].y;
            context.clear_rect(x * 10.0, y * 10.0, 10.0, 10.0);
        }
    }

    fn inc_idx(&mut self) {
        self.idx = (self.idx + 1) % 4;
        for i in 0..4 {
            self.cur_block[i].x = self.possible_block[self.idx][i].x + self.offset.0;
            self.cur_block[i].y = self.possible_block[self.idx][i].y + self.offset.1;
        }
        self.check_valid();
    }

    fn check_valid(&mut self) {
        for i in 0..4 {
            let x = self.cur_block[i].x;
            let y = self.cur_block[i].y;
            if x < 0.0 {
                self.shift(-x, |x, off| x + off);
            }
            if x >= 10.0 {
                self.shift(x-9.0, |x, off| x - off);
            }
        }
    }
}
